//////////////////////////////////////////////
/// CALCULADORA PARA CEGOS
/// 
/// Gestão de Projecto de Software 2013-2014
///
/// Grupo 14:
/// Diogo Moita - 21170937
/// Diogo Oliveira - 21180842
/// Luís Monteiro - 21170270
/// Luís Costa - 21210392
/// Nuno Aguiar - 21160515
/// Pedro Marques - 21160524
//////////////////////////////////////////////
package cvi;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DecimalFormat;

public class CVI {

    Voz voz;
    char ult_sobreBotao;
    String num1;
    String num2;
    String resultado;
    char op;
    boolean isSet1; //Se o primeiro numero ja foi introduzido
    boolean isSet2; //Se o segundo numero ja foi introduzido
    javax.swing.JTextField l_output;
    javax.swing.JTextField l_op;
    // Para não alterar o codigo todo, o sinal do numero será tratado como uma coisa a parte
    // 1 = positivo; -1 = negativo
    double sinal_num1;
    double sinal_num2;
    double sinal_resultado;

    // Construtor recebe uma referencia para a TextField do visor e operador.
    public CVI(javax.swing.JTextField l_output, javax.swing.JTextField l_op) {
        // Inicializações
        voz = new Voz("kevin16"); //existe kevin (baixa qualidade), kevin16 (media qualidade) e alan (alta qualidade mas muito incompleta)
        ult_sobreBotao = ' ';
        num1 = num2 = resultado = "";
        op = 'N';
        isSet1 = isSet2 = false;
        this.l_output = l_output;
        this.l_op = l_op;
        sinal_num1 = sinal_num2 = sinal_resultado = 1;
        actualizarOutput();
        l_output.setText("0");
    }

    // Testar via consola.
    public void consolaTest() throws IOException {
        // Apenas para testar a voz!! O trabalho terá um interface gráfico
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] input = br.readLine().split("\\s+");

        double valor1 = Double.parseDouble(input[0]);
        double valor2 = Double.parseDouble(input[2]);
        char op = input[1].charAt(0);
        double result = new Calcular(valor1, op, valor2).resultado();

        if (result > 99999999 || result < -99999999) // O inferface da calculadora apenas mostra 8 digitos
        {
            voz.diz(voz.ERRO);
        } else {
            System.out.println(valor1 + " " + op + " " + valor2 + " = " + result);
            voz.processaEdiz(valor1, op, valor2, result, false);
        }

        voz.desativar();
    }

    // Diz o nome do botao quando o rato está sobre ele.
    public void sobreBotao(char c) {
        if (ult_sobreBotao != c) {
            ult_sobreBotao = c;
            switch (c) {
                case '*':
                    voz.diz("times");
                    break;
                case '/':
                    voz.diz("divide");
                    break;
                case '-':
                    voz.diz("minus");
                    break;
                case '+':
                    voz.diz("plus");
                    break;
                case 'C':
                    voz.diz("clear");
                    break;
                case 'D':
                    voz.diz("delete");
                    break;
                case 'R':
                    voz.diz("square root");
                    break;
                case 'Q':
                    voz.diz("square");
                    break;
                case 'T':
                    voz.diz("invert");
                    break;
                case '.':
                    voz.diz("point");
                    break;
                case '%':
                    voz.diz("percentage");
                    break;
                case '=':
                    voz.diz("equals");
                    break;
                default:
                    voz.diz(String.valueOf(c));
                    break;
            }
        } else {
            return;
        }
    }

    // Processa o click num determinado botão.
    // c: caracter do botão
    // teclado: se o click veio do teclado ou do interface
    public void clickBotao(char c, boolean teclado) {
        switch (c) {
            case '*':
                adicionarOp('*', teclado);
                break;
            case '/':
                adicionarOp('/', teclado);
                break;
            case '-':
                adicionarOp('-', teclado);
                break;
            case '+':
                adicionarOp('+', teclado);
                break;

            case 'C':
                limparTudo();
                voz.diz("cleared");
                break;

            case 'D':
                apagarUltimo();
                break;

            case 'R':
                op = 'R';
                fazerOperacaoSimples();
                break;

            case 'Q':
                op = 'Q';
                fazerOperacaoSimples();
                break;

            case 'T':
                trocarSinal();
                break;

            case '.':
                adicionarAoNum('.', teclado);
                break;

            case '%':
                fazerOperacao(true);
                break;

            case '=':
                fazerOperacao(false);
                break;

            default:
                adicionarAoNum(c, teclado);
                break;
        }
    }

    // Apaga ultimo digito introduzido.
    void apagarUltimo() {
        if (!isSet1 && num1.length() > 1) {
            num1 = num1.substring(0, num1.length() - 1);
            if (num1.endsWith(".")) {
                num1 = num1.substring(0, num1.length() - 1); // apagar se o ultimo digito passou a ser um ponto
            }
            actualizarOutput();
            voz.diz("deleted");
            return;
        }

        if (!isSet1 && num1.length() == 1) {
            num1 = "0";
            actualizarOutput();
            voz.diz("deleted");
            return;
        }

        if (!isSet2 && num2.length() > 1) {
            num2 = num2.substring(0, num2.length() - 1);
            if (num2.endsWith(".")) {
                num2 = num2.substring(0, num2.length() - 1); // apagar se o ultimo digito passou a ser um ponto
            }
            actualizarOutput();
            voz.diz("deleted");
            return;
        }

        if (!isSet2 && num2.length() == 1) {
            num2 = "0";
            actualizarOutput();
            voz.diz("deleted");
            return;
        }

        voz.diz(voz.AVISO);
        return;
    }

    // Adicionar digito ao numero a ser introduzido
    // c: caracter do botão
    // teclado: se o click veio do teclado ou do interface
    void adicionarAoNum(char c, boolean teclado) {
        if (l_output.getText().equals("ERR")) {
            limparTudo();
        }

        if (op == 'R' || op == 'Q') {
            op = 'N';
        }

        // A introduzir o primeiro numero
        if (!isSet1 || isSet2) {

            // Fez uma operação e vai fazer outra do inicio
            if (isSet2) {
                limparTudo();
            }

            // aviso se tentar meter mais que um ponto
            if (num1.contains(".") && c == '.') {
                voz.diz(voz.AVISO);
                return;
            }

            // aviso se tentar meter uma sequencia de zeros quando numero é zero
            if (num1.equals("0") && c == '0') {
                voz.diz(voz.AVISO);
                return;
            }

            // aviso se tentar ultrapassar os 8 digitos sem ponto
            if (num1.length() == 8 && !num1.contains(".")) {
                voz.diz(voz.AVISO);
                return;
            }

            // aviso se entar ultrapassar os 8 digitos com ponto
            if (num1.length() == 9 && num1.contains(".")) {
                voz.diz(voz.AVISO);
                return;
            }

            // meter 0. se por algum motivo num1 ta vazio
            if (num1.equals("") && c == '.') {
                num1 = "0";
            }

            // apagar o zero caso nao tenha clicado no ponto
            if (num1.equals("0") && c != '.') {
                num1 = "";
            }

            num1 = num1.concat(String.valueOf(c));
            System.out.println("num1 = " + num1);
            actualizarOutput();
            if (teclado) {
                voz.diz(String.valueOf(c));
            } else {
                voz.diz("Clicked " + String.valueOf(c));
            }
            return;
        }

        // primeiro valor já foi introduzido
        if (isSet1) {
            // aviso se tentar meter mais que um ponto
            if (num2.contains(".") && c == '.') {
                voz.diz(voz.AVISO);
                return;
            }

            // aviso se tentar meter uma sequencia de zeros quando numero é zero
            if (num2.equals("0") && c == '0') {
                voz.diz(voz.AVISO);
                return;
            }

            // aviso se tentar ultrapassar os 8 digitos sem ponto
            if (num2.length() == 8 && !num2.contains(".")) {
                voz.diz(voz.AVISO);
                return;
            }

            // aviso se entar ultrapassar os 8 digitos com ponto
            if (num2.length() == 9 && num2.contains(".")) {
                voz.diz(voz.AVISO);
                return;
            }

            // meter 0. se por algum motivo num2 ta vazio
            if (num2.equals("") && c == '.') {
                num2 = "0";
            }

            // apagar o zero caso nao tenha clicado no ponto
            if (num2.equals("0") && c != '.') {
                num2 = "";
            }

            num2 = num2.concat(String.valueOf(c));
            System.out.println("num2 = " + num2);
            actualizarOutput();
            if (teclado) {
                voz.diz(String.valueOf(c));
            } else {
                voz.diz("Clicked " + String.valueOf(c));
            }
            return;
        }
    }

    // Adicionar operador
    // c: caracter do botão
    // teclado: se o click veio do teclado ou do interface
    void adicionarOp(char c, boolean teclado) {
        if (l_output.getText().equals("ERR")) {
            limparTudo();
        }

        // Alterar para a voz nao dizer "asterisco" e "barra"
        String opx = String.valueOf(c);
        if (c == '*') {
            opx = "times";
        }
        if (c == '/') {
            opx = "divide";
        }

        if (c == '-') {
            opx = "minus";
        }

        // Resultado da operação foi apresentado
        if (isSet2 || op == 'Q' || op == 'R') {
            op = c;
            num1 = resultado;
            sinal_num1 = sinal_resultado;
            isSet1 = true;
            isSet2 = false;
            num2 = "";
            sinal_num2 = 1;
            actualizarOutput();
            if (teclado) {
                voz.diz(opx);
            } else {
                voz.diz("Clicked " + opx);
            }
            return;
        }

        // Modo de execuçao normal
        if (!isSet1 || (!num1.equals("") && num2.equals(""))) {
            op = c;
            isSet1 = true;
            isSet2 = false;
            num2 = "";
            sinal_num2 = 1;
            actualizarOutput();
            if (teclado) {
                voz.diz(opx);
            } else {
                voz.diz("Clicked " + opx);
            }
            return;
        }
    }

    void trocarSinal() {
        if (!isSet1 && !num1.equals("0")) {
            sinal_num1 = sinal_num1 * (-1);
            voz.diz("invert");
            actualizarOutput();
            return;
        }

        if (isSet1 && !num2.equals("0")) {
            sinal_num2 = sinal_num2 * (-1);
            voz.diz("invert");
            actualizarOutput();
            return;
        }

        voz.diz(voz.AVISO);
    }

    void limparTudo() {
        num1 = num2 = resultado = "";
        op = 'N';
        isSet1 = false;
        isSet2 = false;
        sinal_num1 = sinal_num2 = sinal_resultado = 1;
        l_output.setText("0");
    }

    // Faz a operação.
    // perc: se é para calcular a percentagem (uma vez que funciona como uma especie de "igual")
    void fazerOperacao(boolean perc) {
        if (isSet1 && !num2.equals("")) {
            isSet2 = true;
            // remover pontos no final caso algo engraçadinho os meteu
            if (num1.endsWith(".")) {
                num1 = num1.substring(0, num1.length() - 1);
            }
            if (num2.endsWith(".")) {
                num2 = num2.substring(0, num2.length() - 1);
            }

            double valor1 = Double.parseDouble(num1);
            double valor2 = Double.parseDouble(num2);
            double result;

            if (!perc) { // Calculo normal
                result = new Calcular(valor1 * sinal_num1, op, valor2 * sinal_num2).resultado();
            } else { // Calculo com percentagem
                System.out.print("% ");
                result = new Calcular(valor1 * sinal_num1, op, (valor2 * sinal_num2) * (valor1 * sinal_num1) / 100).resultado();
            }
            if (result == 999999999) { // Operação deu um erro
                resultado = "ERR";
                actualizarOutput();
                voz.diz(voz.ERRO);
            } else {
                DecimalFormat df = new DecimalFormat("#.#######"); // Necessario para formatar corretamente valores double para string.
                resultado = df.format(result).replace(',', '.'); // converter resultado para string e troca a virgula por um ponto.
                if (resultado.startsWith("-")) { // se o resultado for negativo, alterar.
                    sinal_resultado = -1;
                    resultado = resultado.substring(1);
                } else {
                    sinal_resultado = 1;
                }
                actualizarOutput();
                voz.processaEdiz(valor1, op, valor2, result, perc);
            }
        } else {
            voz.diz(Voz.AVISO);
        }

    }

    // Para as operações com Raiz quadrada, Quadrado
    void fazerOperacaoSimples() {
        // remover pontos no final caso algo engraçadinho os meteu
        if (num1.endsWith(".")) {
            num1 = num1.substring(0, num1.length() - 1);
        }

        double valor1 = Double.parseDouble(l_output.getText());
        double result = new Calcular(valor1, op).resultado();
        System.out.println(String.valueOf(op) + valor1 + " = " + result);
        if (result == 999999999) { // Operação deu um erro
            resultado = "ERR";
            actualizarOutput();
            voz.diz(voz.ERRO);
        } else {
            DecimalFormat df = new DecimalFormat("#.#######"); // Necessario para formatar corretamente valores double para string.
            resultado = df.format(result).replace(',', '.'); // converter resultado para string e troca a virgula por um ponto.
            if (resultado.startsWith("-")) { // se o resultado for negativo, alterar.
                sinal_resultado = -1;
                resultado = resultado.substring(1);
            } else {
                sinal_resultado = 1;
            }

            actualizarOutput();
            voz.processaEdiz(op, valor1, result);
            num1 = num2 = "";
            isSet1 = false;
            isSet2 = false;
            sinal_num1 = sinal_num2 = 1;
        }
    }

    void actualizarOutput() {
        // Mostrar 1º numero
        if (!isSet1) {
            if (sinal_num1 == -1) {
                l_output.setText("-" + num1);
            } else {
                l_output.setText(num1);
            }
        }

        // Mostrar 2º numero
        if (isSet1 && !isSet2 && !num2.equals("")) {
            if (sinal_num2 == -1) {
                l_output.setText("-" + num2);
            } else {
                l_output.setText(num2);
            }
        }

        // Mostrar operador
        if (op != 'N') {
            if (op == '*') {
                l_op.setText("X");
            } else {
                l_op.setText(String.valueOf(op));
            }
        } else {
            l_op.setText(" ");
        }

        // Mostrar resultado
        if (isSet2 || op == 'R' || op == 'Q') {
            if (sinal_resultado == -1) {
                l_output.setText("-" + resultado);
            } else {
                l_output.setText(resultado);
            }
            l_op.setText(" ");
        }
    }

    public void exit() {
        voz.desativar();
        System.exit(0);
    }
    
    public void avisar()
    {
        voz.diz(voz.AVISO);
    }
}